状態を持つ React Component を TDD で実装する
カルーセルは「どのスライドを現在表示しているか」を状態値として持つ必要がある
Step 1: 定義
テストを書く
code:Carousel.test.tsx
describe("Carousel", () => {
it("renders a <div>", () => {
render(<Carousel />);
expect(screen.getByTestId("carousel")).toBeInTheDocument();
});
});
テストをパスする最低限の実装を書く
code:Carousel.tsx
const Carousel = () => <div data-testid="carousel" />;
export default Carousel;
Step 2: スライドのリストを受け取り、そのうちの 1 つを表示する
コンポーネントを実装する際にはコア機能から始めると良い
テストを書く
code:Carousel.test.tsx
const slides = [
{
description: "Slide 1",
attribution: "Uno Pizzeria",
},
{
description: "Slide 2",
attribution: "Dos Equis",
},
{
description: "Slide 3",
attribution: "Three Amigos",
},
];
it("renders the first slide by default", () => {
render(<Carousel slides={slides} />);
const img = screen.getByRole("img");
expect(img).toHaveAttribute("src", slides0.imgUrl); });
テストをパスする最低限の実装を書く
code:Carousel.tsx
type Slide = {
imgUrl?: string;
description?: ReactNode;
attribution?: ReactNode;
};
const Carousel = ({ slides }: { slides: Slide[] }) => {
return (
<div data-testid="carousel">
<CarouselSlide {...slides?.0} /> </div>
);
};
Step 3: Next ボタンをクリックしたときにスライドを進める
warning.icon Enzyme とは異なり、Testing Library は内部状態に直接アクセスできない したがって、コンポーネントの状態をテストするアプローチは以下のようになる
1. コンポーネントをレンダリングする(render)
3. コンポーネントの DOM 出力が期待通りに変化するか確認する テストを書く
code:Carousel.test.tsx
it("renders the slide when the Next button is clicked", async () => {
render(<Carousel slides={slides} />);
const img = screen.getByRole("img");
const nextButton = screen.getByTestId("next-button");
const user = userEvent.setup();
await user.click(nextButton);
expect(img).toHaveAttribute("src", slides1.imgUrl); await user.click(nextButton);
expect(img).toHaveAttribute("src", slides2.imgUrl); await user.click(nextButton);
expect(img).toHaveAttribute("src", slides0.imgUrl); });
await で待機することで、イベントが完全に解決されてテストが続行される前に DOM が更新される typescript-eslint の no-floating-promises ルールで防げる
テストをパスする最低限の実装を書く
code:Carousel.tsx
const Carousel = ({ slides }: { slides?: Slide[] }) => {
return (
<div data-testid="carousel">
<CarouselButton
data-testid="next-button"
onClick={() => {
if (!slides) return;
setSlideIndex((i) => (i + 1) % slides.length);
}}
Next
</CarouselButton>
</div>
);
};
Step 4: Prev ボタンをクリックしたときにスライドを戻す
テストを書く
code:Carousel.test.tsx
it("reverses the slide when the Prev button is clicked", async () => {
render(<Carousel slides={slides} />);
const img = screen.getByRole("img");
const nextButton = screen.getByTestId("prev-button");
const user = userEvent.setup();
await user.click(nextButton);
expect(img).toHaveAttribute("src", slides2.imgUrl); await user.click(nextButton);
expect(img).toHaveAttribute("src", slides1.imgUrl); await user.click(nextButton);
expect(img).toHaveAttribute("src", slides0.imgUrl); });
テストをパスする最低限の実装を書く
code:Carousel.tsx
const Carousel = ({ slides }: { slides?: Slide[] }) => {
return (
<div data-testid="carousel">
<CarouselButton
data-testid="prev-button"
onClick={() => {
if (!slides) return;
setSlideIndex((i) => (i + slides.length - 1) % slides.length);
}}
Prev
</CarouselButton>
<CarouselButton
data-testid="next-button"
onClick={() => {
if (!slides) return;
setSlideIndex((i) => (i + 1) % slides.length);
}}
Next
</CarouselButton>
</div>
);
};